import os
import sys
import subprocess
import random

import buildManager as bm

prettytableImported = True

try:
    from prettytable import PrettyTable 
except:
    print("安装 PTable 模块以获得更好的输出效果")
    prettytableImported = False

helpInfo = '''
使用方法： man.py [目录] [操作] [额外参数...]
支持如下操作：
new     :   在指定目录创建符合build要求结构的基本文件。会覆盖原有的文件。
build   :   在指定目录生成测试数据，并检验checker的正确性。
test    :   在指定目录使用test子目录内已生成好的数据检验sol子目录下的源文件是否能够通过题目。
'''

additionalParameters = {
    'timeLimit': '2s'
}

runCommands = {
    '.py' : 'python3 {}',
    'default' : './{}'
}

enabledOptions = []

def sysc(command):
    commands = command.split('\n')
    for cmd in commands:
        # print(cmd)
        os.system(cmd)

def findSources(directory, fileNames):
    ret = []
    allFile = os.listdir(directory)
    for file in fileNames:
        for k, v in bm.buildCommands.items():
            if file + k in allFile:
                ret.append(file + k)
                break
    return ret

def buildAll(directory, sourceList):
    for file in sourceList:
        bm.build(directory + '/' + file)
    print('\033[1;92mBuild Done\033[0m ')

def compileSolutionsFrom(directory):
    allFile = os.listdir(directory)
    solutions = []
    for file in allFile:
        if bm.buildTo(directory + '/' + file, 'build/sol/' + file.split('.')[0]) == True:
            solutions.append(directory + '/' + file)
    return solutions

def findAllScriptFiles(directory):
    ret = []
    allFile = os.listdir(directory)
    for file in allFile:
        if file.endswith('.in'):
            ret.append(file)
    return ret

def generateTestcases(dir, scriptFile, prefix=''):
    scripts = open(dir + '/' + scriptFile)
    cnt = 0
    failCases = []
    for line in scripts.readlines():
        if line.endswith('\n'):
            line = line[:-1]
        else:
            continue
        if line == 'END':
            continue
        cnt += 1
        fullDir = dir + '/tests/' + prefix + str(cnt)
        #print('ulimit -s 81920000 && ./build/gen ' + line + ' > ' + fullDir + '.in')
        #print('ulimit -s 81920000 && ./build/std ' + ' < ' + fullDir + '.in' + ' > ' + fullDir + '.out')
        #print('ulimit -s 81920000 && ./build/checker ' + ' ' + fullDir + '.in' + ' ' + fullDir + '.out' + ' ' + fullDir + '.out')
        
        os.system('ulimit -s 81920000 && ./build/gen ' + line + ' > ' + fullDir + '.in')
        print("param: " + line)
        returnCode = os.system('ulimit -s 81920000 && timeout ' + additionalParameters['timeLimit'] + ' ./build/std ' + ' < ' + fullDir + '.in' + ' > ' + fullDir + '.out')
        if returnCode > 0:
            print("\033[1;91mFAIL\033[0m std returned " + str(returnCode))
            failCases.append(cnt)
            continue
        os.system('ulimit -s 81920000 && ./build/checker ' + ' ' + fullDir + '.in' + ' ' + fullDir + '.out' + ' ' + fullDir + '.out')
        print()
    print("Generated " + str(cnt - len(failCases)) + "/" + str(cnt) + " test cases from " + scriptFile)
    if len(failCases) > 0:
        print("Failed cases: " + str(failCases))
    return cnt

def findTestcases(directory):
    ret = []
    allFile = os.listdir(directory)
    inFiles = []
    outFiles = []
    for file in allFile:
        if len(file.split('.')) != 2:
            print('Skipped unrecgonized test file ' + directory + '/' + file)
        if file.endswith('.in'):
            inFiles.append(file.split('.')[0])
        if file.endswith('.out'):
            outFiles.append(file.split('.')[0])
    for file in inFiles:
        if file in outFiles:
            ret.append((directory + '/' + file + '.in', directory + '/' + file + '.out'))
    return ret

def testSingleSolution(name, command, testcases):
    failedCases = []
    for inFile, outFile in testcases:
        returnCode = os.system('ulimit -s 81920000 && timeout ' + additionalParameters['timeLimit'] + ' ' + command + ' < ' + inFile + ' > build/temp.out') 
        if returnCode != 0:
            failedCases.append(inFile)
            if returnCode == 31744:
                print('\033[1;91mTime Limit Exceeded\033[0m')
            else:
                print('\033[1;91mRuntime Error\033[0m')
            continue
        if os.system('ulimit -s 81920000 && ./build/checker ' + inFile + ' build/temp.out ' + outFile) != 0:
            failedCases.append(inFile)
    if len(failedCases) != 0:
        print('\033[1;91mREJECTED\033[0m', name, 'Failed Cases:', failedCases)
    else:
        print('\033[1;92mACCEPTED\033[0m', name)
    return len(failedCases)

def getRunCommand(file):
    build = bm.findBuild(file)
    if '.' + file.split('.')[-1] in runCommands.keys():
        return runCommands['.' + file.split('.')[-1]].format(build)
    else:
        return runCommands['default'].format(build)

filteredArgv = []

for parameter in sys.argv:
    if parameter.startswith('-'):
        trace = parameter[1:]
        splitList = trace.split('=')
        if len(splitList) == 2:
            additionalParameters[splitList[0]] = splitList[1]
        else:
            enabledOptions.append(trace)
    else:
        filteredArgv.append(parameter)

projectDirectory = filteredArgv[1]
if projectDirectory.endswith("/") or projectDirectory.endswith("\\"):
    projectDirectory = projectDirectory[:-1]

if "debug" in filteredArgv or "debug" in enabledOptions:
    bm.buildCommands = bm.debugBuildCommands

if "new" in filteredArgv or "new" in enabledOptions:
    sysc('''mkdir -p {0}
cp std.cpp {0}/std.cpp
cp gen.cpp {0}/gen.cpp
cp testlib/checkers/lcmp.cpp {0}/checker.cpp
touch {0}/scripts.in
touch {0}/samples.in'''.format(projectDirectory))
    

for k, v in additionalParameters.items():
    print(k, ": ", v)

if "build" in filteredArgv or "build" in enabledOptions:
    foundFiles = findSources(projectDirectory, ['std', 'gen', 'checker'])
    if len(foundFiles) < 3:
        print("Error: only files " + str(foundFiles) + " found!")
        quit()
    
    buildAll(projectDirectory, foundFiles)
    print(os.popen('mkdir -p ' + projectDirectory + '/tests').read())

    for file in findAllScriptFiles(projectDirectory):
        if file == "scripts.in":
            generateTestcases(projectDirectory, file)
        else:
            generateTestcases(projectDirectory, file, file[:-3])
    

if "test" in filteredArgv or "test" in enabledOptions:
    testcases = findTestcases(projectDirectory + '/tests')
    print(str(len(testcases)) + ' test cases found.')

    os.system('mkdir -p build/sol')
    solutions = compileSolutionsFrom(projectDirectory + '/sol')
    foundFiles = findSources(projectDirectory, ['checker'])
    buildAll(projectDirectory, foundFiles)
    
    results = []

    for file in solutions:
        build = bm.findBuild(file)
        if '.' + file.split('.')[-1] in runCommands.keys():
            results.append((file, testSingleSolution(file, runCommands['.' + file.split('.')[-1]].format(build), testcases)))
        else:
            results.append((file, testSingleSolution(file, runCommands['default'].format(build), testcases)))
    
    if prettytableImported:
        resultTable = PrettyTable()
        resultTable.field_names = ['Source', 'Passed', 'Failed', 'Percentage']
        for sol, result in results:
            resultTable.add_row([sol, len(testcases) - result, result, "{}%".format((len(testcases) - result) / len(testcases) * 100.0)])
        print(resultTable)
    else:
        for sol, result in results:
            print(sol, 'passed', len(testcases) - result, '/', len(testcases), 'cases')

if "stress" in additionalParameters:
    
    os.system('mkdir -p build/stress')
    solutions = compileSolutionsFrom(projectDirectory + '/stress')
    foundFiles = findSources(projectDirectory, ['std', 'checker', 'gen'])
    buildAll(projectDirectory, foundFiles)
    
    failedCases = 0
    while failedCases <= 0:
        randparam = " " + str(random.randint(0, 2 ** 32 - 1))
        print("param: "  + additionalParameters["stress"] + randparam)
        os.system('ulimit -s 81920000 && ./build/gen ' + additionalParameters["stress"] + randparam + ' > build/test.in')
            
        returnCode = os.system('ulimit -s 81920000 && timeout ' + additionalParameters['timeLimit'] + ' ./build/std < build/test.in > build/ans.out')
        if returnCode > 0:
            print("\033[1;91mFAIL\033[0m std returned " + str(returnCode))
            quit()
        
        for sol in solutions:
            returnCode = os.system('ulimit -s 81920000 && timeout ' + additionalParameters['timeLimit'] + ' ' + getRunCommand(sol) + ' < build/test.in > build/temp.out') 
            if returnCode != 0:
                failedCases += 1
                if returnCode == 31744:
                    print('\033[1;91mtime limit exceeded\033[0m')
                else:
                    print('\033[1;91mruntime error\033[0m')
                continue
            if os.system('ulimit -s 81920000 && ./build/checker build/test.in build/temp.out build/ans.out') != 0:
                failedCases += 1
    print("Failed param:\n" + randparam)
